blocky 全 LAN NXDOMAIN 事故与 DNS 分流修复
blocky 全 LAN NXDOMAIN 事故与 DNS 分流修复
来源信息
- 标题:blocky DNS 分流排查与修复记录(2026-09-22)
- 作者:
- 链接:
<local-project-path> - 时间:2026-09-22
- 环境:Talos v1.11.5 / k8s v1.34.0 单节点集群,kubeconfig context
admin@main;配置仓库~/clustertool/(非 git 仓库,用文件 + 时间戳备份管理)
一句话摘要
一次 helm upgrade 后全 LAN 域名大面积 NXDOMAIN:新增的客户端分组只配了 allowlist、没配 denylist,命中 blocky v0.33.0「客户端任一分组是白名单组 ⇒ 整个客户端进入白名单模式」的语义缺陷。回滚后升级到 v0.35.0 恢复,并顺带修复了两处分流缺陷:*.cluster.local 在 blocky 侧无映射、自有域名回落被节点 TUN 劫持成 fake-ip 并被缓存下发到全 LAN。
脱敏说明
真实 IP、域名与主机名已替换为占位符:
| 占位符 | 含义 |
|---|---|
<router-ip> |
家庭网关 / DHCP DNS 下发点 |
<main-dns-ip> |
blocky 的 MetalLB 地址(LAN 主 DNS,:53) |
<mihomo-dns-ip> / <mihomo-service-ip> |
mihomo 的 MetalLB 地址 / 其 DNS 监听端口 1053 |
<talos-node-ip> |
单节点 Talos 主机 |
<ingress-ip> |
内网 nginx ingress(MetalLB) |
<dashboard-ip> / <other-svc-ip> |
其他自建服务的 LB 地址 |
<kube-dns-ip> |
kube-dns ClusterIP |
<k8s-api-vip> |
kubernetes.default ClusterIP |
<blocky-clusterip> / <k8s-gateway-clusterip> |
对应 Service 的 ClusterIP |
<pod-cidr> / <service-cidr> / <lan-cidr> |
集群 Pod 网段 / Service 网段 / 局域网段 |
<home-domain> |
自有域名(k8s_gateway 管理的 zone) |
<corp-domain> |
需要放行的内部业务域名 |
结论摘要
| # | 问题 | 根因 | 处置 |
|---|---|---|---|
| 事故 | 全 LAN 域名大面积 NXDOMAIN,<home-domain>、docker.io、registry.k8s.io、iterm2.com、容器镜像拉取全部失败 |
rev14 给 custom 组只配了 allowlist、没配 denylist,而 blocky v0.33.0 对「客户端任一分组是白名单组」即进入全局 allowlist-only(只有 chart 内置 3 份白名单约 5.6 万条域名能解析) |
先 helm rollback 13 恢复,再升级到 blocky v0.35.0(上游 #2249 已修) |
| 修复 1 | *.cluster.local 在 blocky 侧无映射 → 打到公网上游 NXDOMAIN;mihomo 的 nameserver-policy 指向 blocky 成为死路 |
blocky 缺 cluster.local 映射;mihomo *.cluster.local 的 * 只匹配一级;mihomo 自身 DNS 客户端不可达非回环地址 |
blocky 加 cluster.local → kube-dns;mihomo 过滤器改 +.,策略目标改 127.0.0.53 |
| 修复 2 | 自有域名及其未命中 k8s 资源的子域返回 fake-ip 198.18.x(被 blocky 缓存后下发全 LAN,LAN 不可路由) |
k8s_gateway 的 fallthrough 落到明文 forward . 1.1.1.1,被节点 mihomo TUN 的 :53 劫持 |
打开 chart 提供的 k8sgateway.forward,回落改走 AliDNS DoT(853)(不被劫持) |
最终状态:release rev18(chart blocky-22.25.0 / blocky v0.35.0),blocky 2/2、blocky-redis 1/1、mihomo 1/1,values.yaml 与 release 零漂移。
分流拓扑
flowchart LR
C["LAN 客户端 / Talos 节点 hostDNS"]
R["路由器"]
B["blocky :53<br/>chart blocky-22.25.0 / v0.35.0"]
M["mihomo :1053<br/>fake-ip 198.18.0.0/16"]
G["k8s_gateway 127.0.0.1:5353<br/>zone: 自有域名"]
K["kube-dns :53"]
U["223.5.5.5 / 114.114.114.114"]
I["内网 nginx ingress"]
C --> R --> B
B -->|"≈7.2k 条 GFW + 自定义域名"| M
B -->|"cluster.local"| K
B -->|"自有域名"| G
B -->|"默认"| U
G -->|"未命中 k8s 资源 → DoT 853"| U
G -->|"Ingress / Service / HTTPRoute"| I
blocky 解析顺序:blocking(拦截) → caching → conditional(分流) → upstreams(默认)。
各分叉明细(rev18 实测)
| 目标 | 条件 | 上游 | 说明 |
|---|---|---|---|
| GFW / 自定义代理域名 | conditional.mapping 中约 7.2k 条 |
<mihomo-dns-ip>:1053(mihomo) |
返回 fake-ip 198.18.x,由 mihomo 按规则直连或代理 |
| 集群内部域名 | conditional.mapping: cluster.local |
<kube-dns-ip>:53(kube-dns) |
svc.cluster.local / pod.cluster.local 均覆盖 |
| 集群内网服务入口 | conditional.mapping: <home-domain>(chart 自动注入) |
127.0.0.1:5353(k8s_gateway sidecar) |
由 Ingress/Service/HTTPRoute 生成记录,指向 <ingress-ip> |
| 其余域名 | 无匹配 | 223.5.5.5 / 114.114.114.114(timeout 1s) |
真实地址 |
| 拦截 | 5 个 denylist 组(oisd、StevenBlack、adaway、Easyprivacy、AntiMalware…) | blockType: nxDomain,TTL 6h |
例外:allowlists.default(3 份白名单 URL)+ allowlists.custom(*.<corp-domain>) |
其他关键参数:缓存 minTime 15m / 负缓存 30m,缓存落 redis(valkey,required: true);ports 53/853/4443/4000;HTTPS API 未暴露到 LAN(仅 ClusterIP 4000)。
k8s_gateway:Corefile zone .:5353,apex blocky.blocky,ttl 300,resources Ingress Service HTTPRoute,fallthrough(chart 硬编码,不可通过 values 关闭)。
mihomo:fake-ip-filter 与 nameserver-policy 见下文修复 1;tun.auto-route: true,route-exclude-address 含 <pod-cidr>、<service-cidr>、<lan-cidr>、DoT 出口 IP。
配置来源与部署流程
| 文件 | 作用 |
|---|---|
~/clustertool/myapp/blocky/values.yaml |
blocky 唯一真源(含 ≈7.2k 条 conditional.mapping、blocking、k8sgateway.forward) |
~/clustertool/gfwlist/update-blocky-conditional.sh |
拉取 3 个 GFW 源 + custom-domains.txt,重写 conditional.mapping(custom-domains.txt 中 domain|dnsserver 格式的条目会带自定义上游) |
~/clustertool/gfwlist/{gfw,custom,whitelist}-domains.txt |
域名清单;白名单用于剔除误杀(如某个白名单站点域名) |
~/clustertool/mihomo/configmap.yaml |
mihomo 配置模板(__MIHOMO_SECRET__ 由 init 容器替换后写入 /data/config.yaml) |
~/clustertool/clusters/main/kubernetes/core/blocky/ |
已失效的 Flux GitOps 清单(chart 18.8.2,集群内没有 Flux CRD),不要按它操作 |
部署命令(README 记录的方式):
# 刷新域名列表(可选,会重写 values.yaml 的 mapping 段)
cd ~/clustertool/gfwlist && ./update-blocky-conditional.sh ../myapp/blocky/values.yaml custom-domains.txt whitelist-domains.txt
# 部署 blocky(chart 拉取经 mihomo 代理)
cd ~/clustertool && HTTPS_PROXY=http://<mihomo-service-ip>:7890 \
helm upgrade blocky oci://oci.trueforge.org/truecharts/blocky -f myapp/blocky/values.yaml -n blocky
# 部署 mihomo
cd ~/clustertool && kubectl apply -f mihomo/configmap.yaml && kubectl -n network rollout restart deploy/mihomo
⚠️ blocky 不会可靠地热重载 ConfigMap:kubelet 用原子替换符号链接的方式更新挂载文件,blocky 的 fsnotify 不触发。改完 CM 必须重启 pod(kubectl -n blocky rollout restart deploy/blocky)。
事故:全 LAN 白名单模式(rev14)
时间线
| 时间(+08:00) | 事件 |
|---|---|
| 07-26 23:25 | rev13:chart 22.17.0 / blocky v0.33.0,一切正常 |
| 09-22 18:04:11 | rev14:新增 blocking 块(custom 白名单组 + clientGroupsBlock.default 挂 custom)。该配置只存在于 release,磁盘上没有任何文件记录 |
| 18:50 | 有人刷新了 gfw-domains.txt(7021 → 7164 条),mapping 7029 → 7172 |
| 18:58 | 尝试升级 chart 22.25.0 失败:新 pod ImagePullBackOff(当时误判为镜像问题,实为 DNS) |
| 19:04 | 按要求 helm rollback 13;因 redis STS pod 仍指向 rev15 的镜像摘要、blocky init 容器等 redis 而卡住 → 删除该 pod 后恢复 |
| 19:11 | rev17:DNS 恢复后重跑升级 → 成功(chart 22.25.0 / v0.35.0) |
| 19:19 | rev18:修复 1、2 |
根因
custom 组只有 allowlist、没有同名 denylist,而 blocky v0.33.0 的判定是「客户端任一分组是白名单组 → 整个客户端进入白名单模式」:
// resolver/blocking_resolver.go v0.33.0
func (r *BlockingResolver) hasAllowlistOnlyAllowed(groupsToCheck []string) bool {
for _, group := range groupsToCheck {
if _, found := r.allowlistOnlyGroups[group]; found { return true } // 任一命中即 true
}
return false
}
func determineAllowlistOnlyGroups(cfg *config.Blocking) map[string]bool {
// 只配了 allowlist、没有同名 denylist 的组 → 白名单组
}
默认客户端组包含 custom ⇒ 全 LAN(含经路由器转发的 Talos 节点)只在 chart 内置白名单(anudeepND ×2 + rahilpathan,共 55762 行)内可解析,其余一律 NXDOMAIN,query log 记为 BLOCKED (ALLOWLIST ONLY)。
上游已在 v0.35.0(commit 344de866,"scope allowlist-only mode to the whole client (#2249)", 2026-09-05)改为「客户端所有分组都是白名单组才进入白名单模式」;truecharts chart ≥ 22.23.1 才 pin v0.35.0。
触发原因
<host>.<corp-domain> 命中 chart 内置的 StevenBlack denylist,有人为放行它新增了白名单组 custom,并把 custom 挂进默认客户端组 —— 意图正确,语义踩坑。
影响面
- 全 LAN + 集群节点(Talos
hostDNS→<router-ip>→ blocky)非白名单域名全部 NXDOMAIN; - LAN 上特意走 mihomo 的 registry(
docker.io、quay.io、gcr.io、cgr.dev、factory.talos.dev、registry.k8s.io)、claude.ai、anthropic.com、iterm2.com等全部不可解析; - 集群镜像拉取失败(kubelet 报
not found,其实是 DNS 解析失败;四个镜像摘要在oci.trueforge.org/registry-1.docker.io/mirror.gcr.io实测全部 HTTP 200,并非镜像缺失)。
当前(正确)的例外配置
blocking:
clientGroupsBlock:
- name: default
groups: [default, default-ads, default-tracking, default-malicious, default-suspicious, custom]
whitelist:
- name: custom
lists: ["*.<corp-domain>"]
在 blocky ≥ v0.35.0 下:客户端分组里既有带 denylist 的组、也有白名单组 ⇒ 白名单模式关闭,custom 的 allowlist 退化为「不受 denylist 影响的例外表」,正是预期语义。
修复 1:*.cluster.local 分流
三处协同改动(缺一不可):
| 位置 | 改动 |
|---|---|
myapp/blocky/values.yaml → conditional.mapping |
新增 - domain: cluster.local / dnsserver: <kube-dns-ip>:53 |
gfwlist/custom-domains.txt |
追加 cluster.local|<kube-dns-ip>:53(脚本支持的 domain|dnsserver 格式,避免下次刷新列表被覆盖) |
mihomo/configmap.yaml → fake-ip-filter |
*.cluster.local、*.svc.cluster.local → +.cluster.local、+.svc.cluster.local(mihomo 的 * 只匹配一级,blocky.blocky.svc.cluster.local 会漏) |
mihomo/configmap.yaml → nameserver-policy |
5 个内网策略目标全部改为 127.0.0.53(Talos hostDNS) |
为什么 mihomo 侧要指向回环
mihomo 自身 DNS 客户端在本机不可达任何非回环直连目标,实测(/dns/query 接口):
| 目标 | 结果 |
|---|---|
<main-dns-ip>(blocky LB) |
context deadline exceeded |
tcp://<main-dns-ip>#eth0 |
超时 |
<blocky-clusterip>(blocky ClusterIP) |
超时 |
<kube-dns-ip>(kube-dns ClusterIP) |
超时 |
127.0.0.53(Talos hostDNS) |
✅ 正常返回 <k8s-api-vip> |
tls://1.1.1.1#Proxy(默认 nameserver) |
✅ 正常(走代理出口) |
排除项:tun.dns-hijack: [] 关掉后依旧超时(且会改变 TUN 侧 fake-ip 行为,已回退默认);同一 netns 里 kubectl exec 的 nslookup <main-dns-ip> 正常,说明是 mihomo 进程自身出口的问题。因此内网 DNS 策略必须经回环入口:127.0.0.53 → <router-ip> → blocky → kube-dns。
顺带修复(同一缺陷类)
另外 3 个策略目标(两个云厂商 API 域、一个镜像站域)同样指向不可达的 <main-dns-ip>,一并统一为 127.0.0.53;其中镜像站域在 fake-ip-filter 内(策略会被真正调用),修复前是真实 SERVFAIL。
修复 2:自有域名回落不再返回 fake-ip
chart 把 fallthrough 硬编码在 k8s_gateway 的 Corefile 里,唯一可用旋钮是 k8sgateway.forward:
k8sgateway:
forward:
enabled: true # 原 false(=明文 forward . 1.1.1.1)
primary: tls://223.5.5.5 # chart 默认的 Cloudflare DoT 在本网络实测超时
secondary: tls://223.6.6.6
options:
- name: tls_servername
value: dns.alidns.com
原因链:forward.enabled: false → forward . 1.1.1.1(明文 :53)→ 被节点 mihomo TUN 的 :53 劫持 → 返回 fake-ip(自有域名曾是 198.18.0.37)→ 被 blocky 缓存(负缓存 30m / 正缓存 ≥15m)→ 下发全 LAN,而 198.18.0.0/16 在 LAN 不可路由。DoT(853) 不在劫持范围内,回落恢复真实解析。
实测验证矩阵(rev18 之后)
| 查询 | blocky <main-dns-ip> |
mihomo <mihomo-dns-ip>:1053 |
判定 |
|---|---|---|---|
kubernetes.default.svc.cluster.local |
<k8s-api-vip> |
<k8s-api-vip> |
修复 1 ✅ |
kube-dns.kube-system.svc.cluster.local |
<kube-dns-ip> |
— | ✅ |
blocky.blocky.svc.cluster.local |
<blocky-clusterip> |
<blocky-clusterip> |
✅ |
nope123.default.svc.cluster.local |
NXDOMAIN | NXDOMAIN (Status 3) | 不再超时 / fake-ip ✅ |
<home-domain> / <svc>.<home-domain> |
<ingress-ip> |
fake-ip(mihomo 自身设计) | 修复 2 ✅ |
<home-domain>(直查 k8s_gateway <k8s-gateway-clusterip>:5353) |
<ingress-ip> |
— | 原 198.18.0.37 ✅ |
docker.io / registry.k8s.io / ghcr.io |
198.18.x | 198.18.x | 未受影响 ✅ |
openrouter.ai |
104.18.2/3.115(真实) | 198.18.x | 非白名单域名正常 ✅ |
google.com |
198.18.x | 198.18.x | ✅ |
ad-assets.futurecdn.net |
NXDOMAIN(拦截生效) | fake-ip | ✅ |
<host>.<corp-domain> |
真实公网 IP(不再被 StevenBlack 误杀) | — | 例外表生效 ✅ |
经路由器 <router-ip>(LAN 全路径) |
上述结果一致 | — | ✅ |
其他验证:k8s_gateway 直查五个 Ingress 主机均为 <ingress-ip>;mihomo 代理出口 curl -x http://<mihomo-service-ip>:7890 https://www.gstatic.com/generate_204 → 204;helm -n blocky get values 与 values.yaml 的 mapping 与 k8sgateway.forward 完全一致。
排查方法(可复用的证据链)
- 定位真源:
brew services/ps/find ~ -iname "*blocky*"→~/clustertool;确认 GitOps 目录是死配置(无 Flux CRD)。 - 三层比对:
values.yaml(真源)↔helm get values(release 用户值)↔kubectl get cm blocky-config(渲染后的 config.yml)。本次正是靠helm get values --revision N的逐版本 diff 定位到 rev14 新增的blocking块。 - dig 矩阵定位范围:对照组(白名单内域名)能解析、目标域名 NXDOMAIN ⇒ 指向拦截而非链路问题。
- 读 query log 的
response_reason:kubectl -n blocky logs deploy/blocky -c blocky | grep question_name;BLOCKED (ALLOWLIST ONLY)直接指向上游语义。 - 读对应版本的源码而非最新 main:
raw.githubusercontent.com/0xERR0R/blocky/v0.33.0/resolver/blocking_resolver.go,确认hasAllowlistOnlyAllowed的 ANY 语义,再对比 v0.35.0 的 ALL 语义锁定版本修复。 - 验证「服务健康」的下游链路:绕过故障层直接查 k8s_gateway(
<k8s-gateway-clusterip>:5353)证明该段本身正常,把问题收敛到单一层。 - 对可疑镜像/摘要做外部复核:直接 curl registry manifest,避免把 DNS 故障误判为镜像缺失。
常用排查命令:
# 解析矩阵
for n in <svc>.<home-domain> <home-domain> kubernetes.default.svc.cluster.local docker.io ad-assets.futurecdn.net; do
printf "%-40s %s\n" "$n" "$(dig +short @<main-dns-ip> $n | tr '\n' ' ')"
done
# 拦截/分流原因
kubectl -n blocky logs deploy/blocky -c blocky --tail=200 | grep -oE "question_name=[^ ]+ .*response_reason=[^\"]*"
# 清 DNS 缓存(改配置后建议做一次)
kubectl -n blocky port-forward svc/blocky 14000:4000 &
curl -s -X POST http://127.0.0.1:14000/api/cache/flush
注意事项(已踩过的坑)
- 只配 allowlist 不配 denylist 的组 = 白名单组。在 blocky < v0.35.0 上会因「任一分组命中」而全局生效;务必保持 blocky ≥ v0.35.0(chart ≥ 22.23.1),或给该组补一个同名 denylist。
- mihomo 的
nameserver-policy目标只写回环地址(本机限制,见修复 1);*只匹配一级,需要+.。 - k8s_gateway 的
fallthrough不可配置:关掉forward就是明文1.1.1.1:53,在带 TUN 的节点上会被劫持成 fake-ip 并被缓存下发,必须开启并指向不被劫持的 DoT/DoH。 - 改 CM 必须重启 pod(blocky 不触发文件热重载)。
- 集群相关域名写进
gfwlist/custom-domains.txt(domain|dnsserver格式),否则会被列表刷新脚本覆盖。 - 配置只落在 release 里等于没有配置:rev14 的
blocking块在磁盘上找不到出处,导致排障时先怀疑配置源。所有变更都要落values.yaml。 - 某个镜像站域名目前仍 SERVFAIL,但
223.5.5.5、路由器、blocky 全都 SERVFAIL —— 是该域名上游本身不可解析,与本次配置无关。
回滚与文件清单
回滚:
# blocky:rev17 = 修复 1/2 之前(chart 22.25.0 已就位),rev16 = 事故回滚态
helm -n blocky rollback blocky 17
# mihomo:恢复改动前配置
cd ~/clustertool && cp mihomo/configmap.yaml.bak.20260922192151 mihomo/configmap.yaml \
&& kubectl apply -f mihomo/configmap.yaml && kubectl -n network rollout restart deploy/mihomo
本次涉及的文件与备份(~/clustertool/):
| 文件 | 说明 |
|---|---|
myapp/blocky/values.yaml |
现行真源(含 blocking、k8sgateway.forward、≈7.2k 条 mapping) |
myapp/blocky/values.yaml.bak.20260525202001 |
5 月的旧版本 |
myapp/blocky/values.yaml.bak.20260922185022 |
列表刷新前的状态(无 blocking) |
myapp/blocky/values.yaml.bak.20260922185803 |
插入 blocking 之前 |
myapp/blocky/values.yaml.bak.fix12.20260922191920 |
修复 1/2 之前的 rev17 状态 |
gfwlist/custom-domains.txt |
新增 cluster.local|<kube-dns-ip>:53 |
mihomo/configmap.yaml |
fake-ip-filter 用 +.;内网策略目标 → 127.0.0.53 |
mihomo/configmap.yaml.bak.20260922192151 |
mihomo 改动前状态 |
集群侧:release blocky rev18(chart 22.25.0 / blocky v0.35.0);kube-system/kube-dns ClusterIP <kube-dns-ip>;MetalLB LB 映射 <ingress-ip>(ingress-internal)/<ingress-external-ip>(ingress-external)/<main-dns-ip>(blocky)/<dashboard-ip>(dashboard)/<other-svc-ip>(其他自建服务)/<mihomo-service-ip>(mihomo)。
关联
- k8s 部署 mihomo 问题记录:同一套 blocky + mihomo TUN + fake-ip 分流的部署与踩坑记录;本文是其上线运行后的第一次故障复盘与分流修复。